package com.zx._01_基础._013_io;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

/**
 * 多线程 使用 mmap 技术实现大文件 多线程 分区间 内容 的下载
 */
public class Demo_08_mmap {

    public static void main(String[] args) throws Exception {
        File file = new File("D:\\data\\logs\\debug.log");
        long length = file.length();

        int[][] ranges = getRanges(length);

        // 只打印
        poolPrint(ranges, file);

        // 只下载
        //poolDown(ranges, file);
    }


    private static int[][] getRanges(long length) {
        int count = 100;
        int[][] ranges = new int[count][2];

        int gap = (int) (length / (count - 1));

        // 左闭右开区间[begin, end)
        int begin = 0;
        int end = gap;
        for (int i = 0; i < ranges.length; i++) {
            ranges[i][0] = begin;
            ranges[i][1] = end;

            begin = end;
            end = (int) Math.min(end + gap, length);
        }
        System.out.println("length=" + length);
        for (int i = 0; i < ranges.length; i++) {
            System.out.println(Arrays.toString(ranges[i]));
        }
        return ranges;
    }

    private static void poolPrint(int[][] ranges, File file) {
        ExecutorService pool = Executors.newFixedThreadPool(ranges.length);
        for (int i = 0; i < ranges.length; i++) {
            final int begin = ranges[i][0];
            final int end = ranges[i][1];

            pool.execute(() -> {
                try {
                    MappedByteBuffer inputBuffer = new RandomAccessFile(file, "r").getChannel()
                            .map(FileChannel.MapMode.READ_ONLY,
                                    begin,
                                    end - begin);

                    byte[] bytes = new byte[end - begin];

                    for (int j = 0; j < bytes.length; j++) {
                        bytes[j] = inputBuffer.get();
                    }

                    System.out.print(new String(bytes));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
        pool.shutdown();
    }

    private static void poolDown(int[][] ranges, File file) throws Exception {
        // 这个文件需要是空的
        File newFile = new File("D:\\logs\\1_copy.txt");

        ExecutorService pool = Executors.newFixedThreadPool(ranges.length);
        for (int i = 0; i < ranges.length; i++) {
            final int begin = ranges[i][0];
            final int end = ranges[i][1];

            pool.execute(() -> {
                try (
                        // 区间读数据
                        FileChannel readFileChannel = new RandomAccessFile(file, "r").getChannel();

                        // 区间写数据
                        FileChannel wirteFileChannel = new RandomAccessFile(newFile, "rw").getChannel();
                ) {
                    // 只加载一部分文件内存到mmap中
                    MappedByteBuffer reader = readFileChannel.map(MapMode.READ_ONLY, begin, end - begin);
                    MappedByteBuffer writer = wirteFileChannel.map(MapMode.READ_WRITE, begin, end - begin);

                    for (int j = 0; j < end - begin; j++) {
                        writer.put(reader.get());
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
        pool.shutdown();
    }

    /**
     * 使用 nio 提供的sendFile技术实现文件部分内存的发送
     *
     * @param ranges
     * @param file
     * @throws Exception
     */
    private static void poolSendFile(int[][] ranges, File file, HttpServletResponse response) throws Exception {

        int begin = ranges[5][0];
        int end = ranges[5][1];

        int position = begin;
        int size = end - begin;

        try ( // 区间读数据
                FileChannel readFileChannel = new RandomAccessFile(file, "r").getChannel()) {

            ServletOutputStream outputStream = response.getOutputStream();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
